package com.eternal.base.data.ble;

import android.os.Looper;

import com.clj.fastble.data.BleDevice;
import com.clj.fastble.scan.BleScanRuleConfig;
import com.eternal.base.concat.BluetoothEvent;
import com.eternal.base.concat.DeviceMinModel;
import com.eternal.base.concat.DeviceModel;
import com.eternal.base.concat.DeviceSetting;
import com.eternal.base.concat.DeviceTFP;
import com.eternal.base.concat.HistoryInfo;
import com.eternal.base.database.entity.History;
import com.eternal.base.database.entity.Log;
import com.eternal.base.database.entity.Notification;
import com.eternal.base.global.BluetoothKey;
import com.eternal.base.global.ProgressEvent;
import com.eternal.base.protocol.BaseProtocol;
import com.eternal.base.protocol.CFamilialResolution;
import com.eternal.base.protocol.EFamilialResolution;
import com.eternal.base.protocol.ModelProtocolResolution;
import com.eternal.base.protocol.ProtocolResolution;
import com.eternal.base.utils.ByteUtils;
import com.eternal.base.utils.ScanRecord;
import com.eternal.base.utils.TimeReceiver;
import com.eternal.framework.bus.RxBus;
import com.eternal.framework.bus.RxSubscriptions;
import com.eternal.framework.utils.KLog;

import org.reactivestreams.Publisher;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.reactivex.Completable;
import io.reactivex.CompletableEmitter;
import io.reactivex.CompletableOnSubscribe;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.MaybeEmitter;
import io.reactivex.MaybeOnSubscribe;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.SingleSource;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.BooleanSupplier;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/**
 * 蓝牙控制中心
 */
public class BleController implements IBleControl, Comparator<BleDevice> {
    private BleServer server;
    private HashMap<String, BleStatue> connects;

    public BleController(BleServer server) {
        this.server = server;
        connects = new HashMap<>();
    }

    @Override
    public void clean() {
        server.clear(connects.values());
    }

    @Override
    public void closeScan() {
        server.closeScan();
    }

    @Override
    public Single<List<BleDevice>> scan(BleScanRuleConfig config) {
        return server.scan(config).map(new Function<List<BleDevice>, List<BleDevice>>() {
            @Override
            public List<BleDevice> apply(List<BleDevice> devices) {
                Iterator<BleDevice> iter = devices.iterator();
                BleDevice device;
                ScanRecord record;
                while (iter.hasNext()) {
                    device = iter.next();
                    record = ScanRecord.parseFromBytes(device.getScanRecord());
                    byte[] features = record.getManufacturerSpecificData(BluetoothKey.FEATURES);
                    if (features == null || !ByteUtils.getBit(features[13], 0)) {
                        iter.remove();
                    }
                }
                Collections.sort(devices, BleController.this);
                return devices;
            }
        });
    }

    @Override
    public Observable<List<BleDevice>> scanCycle(BleScanRuleConfig config) {
        return server.scanCycle(config).map(new Function<List<BleDevice>, List<BleDevice>>() {

            @Override
            public List<BleDevice> apply(List<BleDevice> devices) {
                Iterator<BleDevice> iter = devices.iterator();
                BleDevice device;
                ScanRecord record;
                while (iter.hasNext()) {
                    device = iter.next();
                    record = ScanRecord.parseFromBytes(device.getScanRecord());
                    byte[] features = record.getManufacturerSpecificData(BluetoothKey.FEATURES);
                    if (features == null) {// || !ByteUtils.getBit(features[13], 0)
                        iter.remove();
                    }
                }
                Collections.sort(devices, BleController.this);
                return devices;
            }
        });
    }

    @Override
    public List<BleDevice> disableBle() {
        server.reset();
        List<BleDevice> devices = new ArrayList<>(connects.size());
        for (BleStatue statue : connects.values()) {
            if (statue != null) {
                statue.clear();
                devices.add(statue.getDevice());
            }
        }
        connects.clear();
        TimeReceiver.unregister();
        return devices;
    }

    @Override
    public Single<BleStatue> connect(final BleDevice device, final BleServer.DisconnectListener listener) {
        return server.connect(device, new BleServer.DisconnectListener() {
            @Override
            public void onDisconnect(BleDevice device) {
                BleStatue statue = connects.remove(device.getMac());
                if (connects.isEmpty()) {
                    TimeReceiver.unregister();
                }
                if (statue != null) {
                    statue.clear();
                }
                listener.onDisconnect(device);
            }
        }).flatMap(new Function<BleDevice, SingleSource<Integer>>() {
            @Override
            public SingleSource<Integer> apply(BleDevice device) {
                KLog.e("connect setMtu");
                return server.setMtu(device, BluetoothKey.DEFAULT_MTU);
            }
        }).flatMap(new Function<Integer, SingleSource<BleStatue>>() {
            @Override
            public SingleSource<BleStatue> apply(Integer integer) throws Exception {
                KLog.e("connect notify");
                return server.notify(device);
            }
        }).doOnSuccess(new Consumer<BleStatue>() {
            @Override
            public void accept(BleStatue statue) {
                if (connects.isEmpty()) {
                    TimeReceiver.register();
                }
                KLog.e("connect put getMac");
                connects.put(device.getMac(), statue);
            }
        }).doOnError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                KLog.w("connect error disconnect");
                server.clear(device);
            }
        }).doOnDispose(new Action() {
            @Override
            public void run() {
                KLog.w("dispose connect disconnecting");
                server.clear(device);
            }
        });
    }

    @Override
    public void disConnect(BleStatue statue) {
        statue.clear();
        connects.remove(statue.getDevice().getMac());
        server.clear(statue.getDevice());
    }

    @Override
    public void disConnectC() {
        Iterator<BleStatue> it= connects.values().iterator();
        while (it.hasNext()) {
            BleStatue statue = it.next();
            if (statue != null && statue.getType() >= 3) {
                statue.clear();
                server.clear(statue.getDevice());
                it.remove();
            }
        }
    }

    @Override
    public BleStatue getConnect(String mac) {
        return connects.get(mac);
    }

    @Override
    public boolean isConnect(String mac) {
        return connects.containsKey(mac);
    }

    public Single<Boolean> syncTime(BleStatue statue) {
        SingleMessage message = new SingleMessage(ProtocolResolution.setTimeData(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle()
                .map(new Function<byte[], Boolean>() {
                    @Override
                    public Boolean apply(byte[] bytes) throws Exception {
                        return BaseProtocol.checkResult(bytes);
                    }
                });
    }

    @Override
    public Single<Boolean> syncCTime(BleStatue statue) {
        SingleMessage message = new SingleMessage(CFamilialResolution.setTime(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(@NonNull byte[] bytes) {
                KLog.e("syncCTime success!");
                return BaseProtocol.checkResult(bytes);
            }
        });
    }

    public Single<Boolean> syncETime(BleStatue statue) {
        SingleMessage message = new SingleMessage(EFamilialResolution.setTimeData(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle()
                .map(new Function<byte[], Boolean>() {
                    @Override
                    public Boolean apply(byte[] bytes) throws Exception {
                        return EFamilialResolution.checkResult(bytes);
                    }
                });
    }

    @Override
    public Completable syncConnectedTime() {
        List<Single<Boolean>> list = new ArrayList<>(connects.size());
        for (BleStatue statue : connects.values()) {
            if (statue.getType() == BluetoothKey.DEVICE_TYPE_A || statue.getType() == BluetoothKey.DEVICE_TYPE_B) {
                list.add(syncTime(statue));
            } else if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                list.add(syncETime(statue));
            }else {
                list.add(syncCTime(statue));
            }
        }
        return Single.merge(list).ignoreElements();
    }

    @Override
    public Single<DeviceTFP> getTFP(String mac, final byte command) {
        final BleStatue statue = connects.get(mac);
        return Single.create(new SingleOnSubscribe<SingleMessage>() {
            @Override
            public void subscribe(SingleEmitter<SingleMessage> emitter) {
                byte [] packet;
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    packet = EFamilialResolution.getTFPData(command, statue.getLastSequence());
                } else {
                    packet = ProtocolResolution.getTFPData(command, statue.getLastSequence());
                }
                SingleMessage message = new SingleMessage(packet);
                server.tryWrite(statue, message);
                emitter.onSuccess(message);
            }
        }).flatMap(new Function<SingleMessage, SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(SingleMessage singleMessage) throws Exception {
                return singleMessage.getSingle();
            }
        }).map(new Function<byte[], DeviceTFP>() {
            @Override
            public DeviceTFP apply(byte[] bytes) throws Exception {
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    return EFamilialResolution.parseTFP(bytes);
                } else {
                    return ProtocolResolution.parseTFP(bytes);
                }
            }
        });
    }


    @Override
    public Observable<DeviceModel> getBroadcast(BleStatue statue) {
        if (statue == null) {
            return null;
        }
        return statue.getBroadcast();
    }

    @Override
    public Single<DeviceMinModel> getCModel(final BleStatue statue) {
        return Single.create(new SingleOnSubscribe<SingleMessage>() {
            @Override
            public void subscribe(SingleEmitter<SingleMessage> emitter) throws Exception {
                SingleMessage message = new SingleMessage(ModelProtocolResolution.getCModelAlarmData(statue.getLastSequence()));
                server.tryWrite(statue, message);
                emitter.onSuccess(message);
            }
        }).flatMap(new Function<SingleMessage, SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(SingleMessage singleMessage) throws Exception {
                return singleMessage.getSingle();
            }
        }).map(new Function<byte[], DeviceMinModel>() {
            @Override
            public DeviceMinModel apply(byte[] bytes) throws Exception {
                return ModelProtocolResolution.parseCMinModel(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> setCModel(BleStatue statue, DeviceModel model) {
        SingleMessage message = new SingleMessage(CFamilialResolution.setModelData(model, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) {
                return CFamilialResolution.checkResult(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> setSetting(String mac, DeviceSetting setting) {
        BleStatue statue = connects.get(mac);
        SingleMessage message = new SingleMessage(ProtocolResolution.setSettingData(setting, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) {
                return ProtocolResolution.checkResult(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> setBSetting(String mac, DeviceSetting setting) {
        BleStatue statue = connects.get(mac);
        SingleMessage message = new SingleMessage(ProtocolResolution.setBSettingData(setting, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) throws Exception {
                return ProtocolResolution.checkResult(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> setCSetting(String mac, DeviceSetting setting) {
        BleStatue statue = connects.get(mac);
        SingleMessage message = new SingleMessage(CFamilialResolution.setSettingData(setting, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) throws Exception {
                return BaseProtocol.checkResult(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> setESetting(String mac, byte port, DeviceSetting setting) {
        BleStatue statue = connects.get(mac);
        SingleMessage message = new SingleMessage(EFamilialResolution.setSettingData(port, setting, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) throws Exception {
                return EFamilialResolution.checkResult(bytes);
            }
        });
    }


    @Override
    public Single<DeviceSetting> getSetting(BleStatue statue) {
        SingleMessage message = new SingleMessage(ProtocolResolution.getSettingData(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], DeviceSetting>() {
            @Override
            public DeviceSetting apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseSetting(bytes);
            }
        });
    }

    @Override
    public Single<DeviceSetting> getBSetting(BleStatue statue) {
        SingleMessage message = new SingleMessage(ProtocolResolution.getBSettingData(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], DeviceSetting>() {
            @Override
            public DeviceSetting apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseSetting(bytes);
            }
        });
    }

    @Override
    public Single<DeviceSetting> getCSetting(BleStatue statue) {
        SingleMessage message = new SingleMessage(CFamilialResolution.getSettingData(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], DeviceSetting>() {
            @Override
            public DeviceSetting apply(byte[] bytes) {
                return CFamilialResolution.parseSetting(bytes);
            }
        });
    }

    @Override
    public Single<DeviceSetting> getESetting(BleStatue statue, byte port) {
        SingleMessage message = new SingleMessage(EFamilialResolution.getSettingData(port,statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], DeviceSetting>() {
            @Override
            public DeviceSetting apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseSetting(bytes);
            }
        });
    }

    @Override
    public Single<Boolean> getTempUnit(BleStatue statue) {
        byte [] packet;
        if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
            packet = EFamilialResolution.getTempUnitData((byte)0x00,statue.getLastSequence());
        } else {
            packet = ProtocolResolution.getTempUnitData(statue.getLastSequence());
        }
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) {
                return ProtocolResolution.parseTempUnit(bytes);
            }
        });
    }

    @Override
    public Single<DeviceMinModel> getModel(final BleStatue statue,final byte port) {
        final DeviceMinModel model = new DeviceMinModel();

        return Single.create(new SingleOnSubscribe<SingleMessage>() {
            @Override
            public void subscribe(SingleEmitter<SingleMessage> emitter) throws Exception {
                byte [] packet;
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    packet = EFamilialResolution.getModelData(port, statue.getLastSequence());
                } else {
                    packet = ProtocolResolution.getModelData(statue.getLastSequence());
                }
                SingleMessage message = new SingleMessage(packet);
                server.tryWrite(statue, message);
                emitter.onSuccess(message);
            }
        }).flatMap(new Function<SingleMessage, SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(SingleMessage singleMessage) throws Exception {
                return singleMessage.getSingle();
            }
        }).flatMap(new Function<byte[], SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(byte[] bytes) throws Exception {
                ProtocolResolution.parseMinModel(model, bytes);
                byte [] packet;
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    packet = getEModeData(statue, port ,model.model);
                } else {
                    packet = getModeData(statue, model.model);
                }
                SingleMessage message = new SingleMessage(packet);
                server.tryWrite(statue, message);
                return message.getSingle();
            }
        }).map(new Function<byte[], DeviceMinModel>() {
            @Override
            public DeviceMinModel apply(byte[] bytes) throws Exception {
                ProtocolResolution.initDeviceMinModel(model, bytes);
                model.port = port;
                return model;
            }
        });
    }

    private byte[] getModeData(BleStatue statue, byte model) {
        switch (model) {
            case 0x1:
                return ProtocolResolution.getOffModelData(statue.getLastSequence());
            case 0x2:
                return ProtocolResolution.getOnModelData(statue.getLastSequence());
            case 0x3:
                return ProtocolResolution.getAutoModelData(statue.getLastSequence());
            case 0x4:
                return ProtocolResolution.getTimeOnModelData(statue.getLastSequence());
            case 0x5:
                return ProtocolResolution.getTimeOffModelData(statue.getLastSequence());
            case 0x6:
                return ProtocolResolution.getCycleModelData(statue.getLastSequence());
            default:
                return ProtocolResolution.getSchedModelData(statue.getLastSequence());
        }
    }

    private byte[] getEModeData(BleStatue statue,byte port, byte model) {
        switch (model) {
            case 0x1:
                return EFamilialResolution.getOffModelData(port, statue.getLastSequence());
            case 0x2:
                return EFamilialResolution.getOnModelData(port, statue.getLastSequence());
            case 0x3:
                return EFamilialResolution.getAutoModelData(port, statue.getLastSequence());
            case 0x4:
                return EFamilialResolution.getTimeOnModelData(port, statue.getLastSequence());
            case 0x5:
                return EFamilialResolution.getTimeOffModelData(port, statue.getLastSequence());
            case 0x6:
                return EFamilialResolution.getCycleModelData(port, statue.getLastSequence());
            default:
                return EFamilialResolution.getSchedModelData(port, statue.getLastSequence());
        }
    }

    @Override
    public Single<Boolean> setModel(BleStatue statue, byte port, DeviceModel model) {
        byte [] packet ;
        if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
            packet = EFamilialResolution.setModelData(port, model, statue.getLastSequence());
        } else {
            packet = ProtocolResolution.setModelData(model, statue.getLastSequence());
        }
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle()
                .map(new Function<byte[], Boolean>() {
                    @Override
                    public Boolean apply(byte[] bytes) throws Exception {
                        return ProtocolResolution.checkResult(bytes);
                    }
                });
    }

    @Override
    public Single<Boolean> setModel(BleStatue statue, byte port, byte model) {
        byte [] packet ;
        if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
            packet = EFamilialResolution.setModelData(port, model, statue.getLastSequence());
        } else {
            packet = ProtocolResolution.setModelData(model, statue.getLastSequence());
        }
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle()
                .map(new Function<byte[], Boolean>() {
                    @Override
                    public Boolean apply(byte[] bytes) throws Exception {
                        return ProtocolResolution.checkResult(bytes);
                    }
                });
    }

    @Override
    public Single<DeviceModel> initMode(BleStatue statue, final byte port) {
        byte [] packet ;
        if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
            packet = EFamilialResolution.getAllModelData(port,statue.getLastSequence());
        } else {
            packet = ProtocolResolution.getAllModelData(statue.getLastSequence());
        }
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle()
                .map(new Function<byte[], DeviceModel>() {
                    @Override
                    public DeviceModel apply(byte[] bytes) {
                        DeviceModel model = ProtocolResolution.parseDeviceModel(bytes);
                        model.port = port;
                        return model;
                    }
                });
    }

    @Override
    public Single<Boolean> setNotification(BleStatue statue,Notification notification, boolean isExit, boolean isOpen) {
        byte [] packet ;
        if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
            packet = EFamilialResolution.setNotificationData(notification, isExit,
                    isOpen, statue.getLastSequence());
        } else {
            packet = ProtocolResolution.setNotificationData(notification, isExit,
                    isOpen, statue.getLastSequence());
        }
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) throws Exception {
                boolean result = ProtocolResolution.checkResult(bytes);
                if (!result) {
                    KLog.e("result false:" + ByteUtils.bytes2HexString(bytes));
                }
                return result;
            }
        });
    }

    @Override
    public Single<List<Notification>> getNotifications(final BleStatue statue, final byte port) {
        return Single.create(new SingleOnSubscribe<SingleMessage>() {
            @Override
            public void subscribe(SingleEmitter<SingleMessage> emitter) throws Exception {
                byte[] packet;
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    packet = EFamilialResolution.getNotificationData(port,statue.getLastSequence());
                } else {
                    packet = ProtocolResolution.getNotificationData(statue.getLastSequence());
                }
                SingleMessage message = new SingleMessage(packet);
                server.tryWrite(statue, message);
                emitter.onSuccess(message);
            }
        }).flatMap(new Function<SingleMessage, SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(SingleMessage singleMessage) throws Exception {
                return singleMessage.getSingle();
            }
        }).map(new Function<byte[], List<Notification>>() {
            @Override
            public List<Notification> apply(byte[] bytes) throws Exception {
                if (statue.getType() == BluetoothKey.DEVICE_TYPE_E) {
                    return EFamilialResolution.parseNotifications(port,bytes);
                } else {
                    return ProtocolResolution.parseNotifications(bytes);
                }
            }
        });
    }

    @Override
    public Single<List<Notification>> getNotifications(BleStatue statue) {
        return Single.create(new SingleOnSubscribe<SingleMessage>() {
            @Override
            public void subscribe(SingleEmitter<SingleMessage> emitter) throws Exception {
                SingleMessage message = new SingleMessage(ProtocolResolution.getNotificationData(statue.getLastSequence()));
                server.tryWrite(statue, message);
                emitter.onSuccess(message);
            }
        }).flatMap(new Function<SingleMessage, SingleSource<byte[]>>() {
            @Override
            public SingleSource<byte[]> apply(SingleMessage singleMessage) throws Exception {
                return singleMessage.getSingle();
            }
        }).map(new Function<byte[], List<Notification>>() {
            @Override
            public List<Notification> apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseNotifications(bytes);
            }
        });
    }

    @Override
    public Observable<Log> getLog(final BleStatue statue, long start, final int id) {
        long time = System.currentTimeMillis();
        final LogParse parse = new LogParse(time);
        SingleMessage message = new SingleMessage(ProtocolResolution.getLogStart(start, time, id, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Integer>() {
            @Override
            public Integer apply(byte[] bytes) {
//                KLog.e("reciever log size mac:" +45 statue.getDevice().getMac());
                return ProtocolResolution.parseLogSize(bytes);
            }
        }).flatMapObservable(new Function<Integer, ObservableSource<Log>>() {
            private int total, now;

            @Override
            public ObservableSource<Log> apply(Integer integer) {
                total = integer;
                now = 0;
                return Single.create(new SingleOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(SingleEmitter<ObservableMessage> emitter) throws Exception {
                        ObservableMessage m = new ObservableMessage(ProtocolResolution.getLog(total, now, statue.getLastSequence()));
                        server.tryWrite(statue, m);
                        emitter.onSuccess(m);
                    }
                }).flatMapObservable(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(ObservableMessage message) {
                        return message.getSingle();
                    }
                }).flatMap(new Function<byte[], ObservableSource<Log>>() {
                    @Override
                    public ObservableSource<Log> apply(byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse).doOnComplete(new Action() {
                            @Override
                            public void run() {
                                if (parse.total == -1) {
                                    server.onComplement(statue);
                                }
                            }
                        });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() {
                        return total < ++now;
                    }
                });
            }
        });
    }

    @Override
    public Observable<Log> getELog(final BleStatue statue, final byte port, long start, final int id) {
        long time = System.currentTimeMillis();
        final LogParse parse = new LogParse(time);
        SingleMessage message = new SingleMessage(EFamilialResolution.getLogStart(port,start, time, id, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Integer>() {
            @Override
            public Integer apply(byte[] bytes) {
//                KLog.e("reciever log size mac:" +45 statue.getDevice().getMac());
                return EFamilialResolution.parseLogSize(bytes);
            }
        }).flatMapObservable(new Function<Integer, ObservableSource<Log>>() {
            private int total, now;

            @Override
            public ObservableSource<Log> apply(Integer integer) {
                total = integer;
                now = 0;
                return Single.create(new SingleOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(SingleEmitter<ObservableMessage> emitter) throws Exception {
                        ObservableMessage m = new ObservableMessage(EFamilialResolution.getLog(port,total, now, statue.getLastSequence()));
                        server.tryWrite(statue, m);
                        emitter.onSuccess(m);
                    }
                }).flatMapObservable(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(ObservableMessage message) {
                        return message.getSingle();
                    }
                }).flatMap(new Function<byte[], ObservableSource<Log>>() {
                    @Override
                    public ObservableSource<Log> apply(byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse).doOnComplete(new Action() {
                            @Override
                            public void run() {
                                if (parse.total == -1) {
                                    server.onComplement(statue);
                                }
                            }
                        });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() {
                        return total < ++now;
                    }
                });
            }
        });
    }

    @Override
    public Observable<Log> getCLog(final BleStatue statue, final long start) {
        KLog.e("refresh c log");
        final LogCParse parse = new LogCParse();
        SingleMessage message = new SingleMessage(CFamilialResolution.getInfo(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().flatMap(new Function<byte[], SingleSource<int[]>>() {
            @Override
            public SingleSource<int[]> apply(@NonNull byte[] bytes) {
                final int[] params = new int[2];
                KLog.e("result" + ByteUtils.bytes2HexString(bytes));
                params[0] = CFamilialResolution.parseLogSize(bytes);
                parse.setList(CFamilialResolution.parseHistoryInfo(bytes));
                SingleMessage idSm = new SingleMessage(CFamilialResolution.getLogId(start, statue.getLastSequence()));
                server.tryWrite(statue, idSm);
                return idSm.getSingle().map(new Function<byte[], int[]>() {
                    @Override
                    public int[] apply(@NonNull byte[] bytes) {
                        params[1] = CFamilialResolution.parseLogId(bytes);
                        return params;
                    }
                });
            }
        }).flatMapObservable(new Function<int[], ObservableSource<Log>>() {
            private int total, now;

            @Override
            public ObservableSource<Log> apply(@NonNull int[] ints) throws Exception {
                total = ints[0];
                now = ints[1];
                return Maybe.create(new MaybeOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(@NonNull MaybeEmitter<ObservableMessage> emitter) throws Exception {
                        int size = total - now;
                        if (size <= 0) {
                            emitter.onComplete();
                        } else {
                            ObservableMessage logSm;
                            if (size < 20) {
                                logSm = new ObservableMessage(CFamilialResolution.getLog(now, size, statue.getLastSequence()));
                            } else {
                                logSm = new ObservableMessage(CFamilialResolution.getLog(now, 20, statue.getLastSequence()));
                            }
                            server.tryWrite(statue, logSm);
                            emitter.onSuccess(logSm);
                        }
                    }
                }).flatMapObservable(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(@NonNull ObservableMessage msg) throws Exception {
                        return msg.getSingle();
                    }
                }).flatMap(new Function<byte[], ObservableSource<Log>>() {
                    @Override
                    public ObservableSource<Log> apply(@NonNull byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse).doOnComplete(new Action() {
                            @Override
                            public void run() {
                                if (parse.total == -1) {
                                    server.onComplement(statue);
                                }
                            }
                        });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() throws Exception {
                        now += 20;
                        return now >= total;
                    }
                });
            }
        });
    }

    @Override
    public Observable<History> getHistory(final BleStatue statue, final long start) {
        long time = System.currentTimeMillis();
        final HistoryParse parse = new HistoryParse(time);
        byte [] packet = ProtocolResolution.getHistoryStart(start, time, statue.getLastSequence());
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Integer>() {
            @Override
            public Integer apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseLogSize(bytes);
            }
        }).flatMapObservable(new Function<Integer, ObservableSource<History>>() {
            private int total, now;

            @Override
            public ObservableSource<History> apply(Integer integer) {
                total = integer;
                now = 0;
                return Observable.create(new ObservableOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(ObservableEmitter<ObservableMessage> emitter) throws Exception {
                        byte [] packet  = ProtocolResolution.getHistory(total, now, statue.getLastSequence());
                        ObservableMessage m = new ObservableMessage(packet);
                        server.tryWrite(statue, m);
                        emitter.onNext(m);
                        emitter.onComplete();
                    }
                }).flatMap(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(final ObservableMessage observableMessage) throws Exception {
                        return observableMessage.getSingle().doOnDispose(new Action() {
                            @Override
                            public void run() {
                                observableMessage.reset().flatMap(new Function<byte[], ObservableSource<?>>() {
                                    @Override
                                    public ObservableSource<?> apply(byte[] bytes) {
                                        parse.setNow(bytes);
                                        return Observable.create(parse).doOnComplete(new Action() {
                                            @Override
                                            public void run() {
                                                if (parse.isEnd()) {
                                                    server.onComplement(statue);
                                                }
                                            }
                                        });
                                    }
                                }).ignoreElements().subscribe();
                            }
                        });
                    }
                }).flatMap(new Function<byte[], ObservableSource<History>>() {
                    @Override
                    public ObservableSource<History> apply(byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse)
                                .doOnComplete(new Action() {
                                    @Override
                                    public void run() throws Exception {
                                        if (parse.isEnd()) {
                                            server.onComplement(statue);
                                        }
                                    }
                                });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() throws Exception {
                        ++now;
                        RxBus.getDefault().post(new ProgressEvent(ProgressEvent.SET, now / (float) total * 100));
                        return total < now;
                    }
                });
            }
        });
    }

    @Override
    public Observable<History> getEHistory(final BleStatue statue, final long start) {
        long time = System.currentTimeMillis();
        final HistoryEParse parse = new HistoryEParse(time);
        byte [] packet = ProtocolResolution.getHistoryStart(start, time, statue.getLastSequence());
        SingleMessage message = new SingleMessage(packet);
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Integer>() {
            @Override
            public Integer apply(byte[] bytes) throws Exception {
                return ProtocolResolution.parseLogSize(bytes);
            }
        }).flatMapObservable(new Function<Integer, ObservableSource<History>>() {
            private int total, now;

            @Override
            public ObservableSource<History> apply(Integer integer) {
                total = integer;
                now = 0;
                return Observable.create(new ObservableOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(ObservableEmitter<ObservableMessage> emitter) throws Exception {
                        byte [] packet  = ProtocolResolution.getHistory(total, now, statue.getLastSequence());
                        ObservableMessage m = new ObservableMessage(packet);
                        server.tryWrite(statue, m);
                        emitter.onNext(m);
                        emitter.onComplete();
                    }
                }).flatMap(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(final ObservableMessage observableMessage) throws Exception {
                        return observableMessage.getSingle().doOnDispose(new Action() {
                            @Override
                            public void run() {
                                observableMessage.reset().flatMap(new Function<byte[], ObservableSource<?>>() {
                                    @Override
                                    public ObservableSource<?> apply(byte[] bytes) {
                                        parse.setNow(bytes);
                                        return Observable.create(parse).doOnComplete(new Action() {
                                            @Override
                                            public void run() {
                                                if (parse.isEnd()) {
                                                    server.onComplement(statue);
                                                }
                                            }
                                        });
                                    }
                                }).ignoreElements().subscribe();
                            }
                        });
                    }
                }).flatMap(new Function<byte[], ObservableSource<History>>() {
                    @Override
                    public ObservableSource<History> apply(byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse)
                                .doOnComplete(new Action() {
                                    @Override
                                    public void run() throws Exception {
                                        if (parse.isEnd()) {
                                            server.onComplement(statue);
                                        }
                                    }
                                });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() throws Exception {
                        ++now;
                        RxBus.getDefault().post(new ProgressEvent(ProgressEvent.SET, now / (float) total * 100));
                        return total < now;
                    }
                });
            }
        });
    }


    private int getHistoryInfo(List<HistoryInfo> infos, long start) {
        if (infos.size() == 1) {
            return 0;
        }
        int index, left = 0, right = infos.size() - 1;
        HistoryInfo info;
        while (left + 1 != right) {
            index = (left + right) / 2;
            info = infos.get(index);
            if (info.calibrated > start) {
                right = index;
            } else if (info.calibrated < start) {
                left = index;
            } else {
                return index;
            }
        }
        return left;
    }

    @Override
    public Observable<History> getCHistory(final BleStatue statue, final long start) {
        final HistoryCParse parse = new HistoryCParse();
        SingleMessage message = new SingleMessage(CFamilialResolution.getInfo(statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], List<HistoryInfo>>() {
            @Override
            public List<HistoryInfo> apply(@NonNull byte[] bytes) {
                return CFamilialResolution.parseHistoryInfo(bytes);
            }
        }).flatMapObservable(new Function<List<HistoryInfo>, ObservableSource<History>>() {
            private int chunk, index, total, now;
            private HistoryInfo info;

            @Override
            public ObservableSource<History> apply(@NonNull final List<HistoryInfo> infos) {
                chunk = getHistoryInfo(infos, start);
                info = infos.get(chunk);
                total = 0;
                if (info.calibrated >= start) {
                    index = 0;
                } else if (info.calibrated + info.length < start) {
                    index = 0;
                    chunk += 1;
                    try {
                        info = infos.get(chunk);
                    } catch (Exception e) {
                        return Observable.empty();
                    }
                    if (info.calibrated + info.length < start) {
                        return Observable.empty();
                    } else {
                        index = (int) start - info.calibrated;
                    }
                } else {
                    index = (int) start - info.calibrated;
                }
                total = info.length - index;
                for (int i = chunk + 1; i < infos.size(); i++) {
                    total += infos.get(i).length;
                }
                return Single.create(new SingleOnSubscribe<ObservableMessage>() {
                    @Override
                    public void subscribe(@NonNull SingleEmitter<ObservableMessage> emitter) throws Exception {
                        ObservableMessage obm;
                        info = infos.get(chunk);
                        if (info.length - index > 40) {
                            obm = new ObservableMessage(CFamilialResolution.getHistory(info.startId + index, 40, statue.getLastSequence()));
                        } else {
                            obm = new ObservableMessage(CFamilialResolution.getHistory(info.startId + index, info.length - index, statue.getLastSequence()));
                        }
                        server.tryWrite(statue, obm);
                        parse.setTime(info.calibrated + index);
                        emitter.onSuccess(obm);
                    }
                }).flatMapObservable(new Function<ObservableMessage, ObservableSource<byte[]>>() {
                    @Override
                    public ObservableSource<byte[]> apply(@NonNull final ObservableMessage observableMessage) {
                        return observableMessage.getSingle().doOnDispose(new Action() {
                            @Override
                            public void run() {
                                observableMessage.reset().flatMap(new Function<byte[], ObservableSource<?>>() {
                                    @Override
                                    public ObservableSource<?> apply(byte[] bytes) {
                                        parse.setNow(bytes);
                                        return Observable.create(parse).doOnComplete(new Action() {
                                            @Override
                                            public void run() {
                                                if (parse.isEnd()) {
                                                    server.onComplement(statue);
                                                }
                                            }
                                        });
                                    }
                                }).ignoreElements().subscribe();
                            }
                        });
                    }
                }).flatMap(new Function<byte[], ObservableSource<History>>() {
                    @Override
                    public ObservableSource<History> apply(@NonNull byte[] bytes) {
                        parse.setNow(bytes);
                        return Observable.create(parse).doOnComplete(new Action() {
                            @Override
                            public void run() {
                                if (parse.isEnd()) {
                                    server.onComplement(statue);
                                }
                            }
                        });
                    }
                }).repeatUntil(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() {
                        info = infos.get(chunk);
                        index += 40;
                        now += 40;
                        if (info.length <= index) {
                            now -= index - info.length;
                            chunk++;
                            if (chunk < infos.size()) {
                                index = 0;
                            } else {
                                RxBus.getDefault().post(new ProgressEvent(ProgressEvent.SET, 100));
                                return true;
                            }
                        }
                        RxBus.getDefault().post(new ProgressEvent(ProgressEvent.SET, now / (float) total * 100));
                        return false;
                    }
                });
            }
        });
    }

    @Override
    public Single<Boolean> setChoosePort(BleStatue statue, byte port) {
        SingleMessage message = new SingleMessage(EFamilialResolution.setChoosePort(port, statue.getLastSequence()));
        server.tryWrite(statue, message);
        return message.getSingle().map(new Function<byte[], Boolean>() {
            @Override
            public Boolean apply(byte[] bytes) throws Exception {
                boolean result = ProtocolResolution.checkResult(bytes);
                if (!result) {
                    KLog.e("result false:" + ByteUtils.bytes2HexString(bytes));
                }
                return result;
            }
        });
    }

    @Override
    public int compare(BleDevice o1, BleDevice o2) {
        return o2.getRssi() - o1.getRssi();
    }
}
